﻿#include "opencv2/opencv.hpp"
#include <opencv2/highgui/highgui.hpp>
//#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

//4. 使用光流估计方法，在前述测试视频上计算特征点，进一步进行特征点光流估计

/*
1. 视频采集(取到视频中当前帧图像) :VideoCapture
2. 图像预处理 
3. 提取特征点
4. 使用光流法估计特征点运动：比较两帧，当前帧和上一帧进行交换
5. 相邻帧及特征点交换
*/
/*结果：光流估计检测到的特征点较少，且灰度变化不大的区域，检测不到。*/
int main()
{
	string path = "E:\\AI\\AIsoftware\\opencv\\sources\\samples\\data\\vtest.avi";
	VideoCapture cap;
	Mat source, result,gray,lastGray;  //gray,lastGray对应上一帧和本帧的灰度图
	vector<Point2f> points[2], temp;   //对应上一帧和本帧的特征点上一帧是给定的，本帧时预测结果；points[0]为上一帧检测到的所有角点，points[1]对应的为上一帧的角点通过光流估计运动到这一帧的哪个位置
	vector<uchar> status;              //每一特征点检测状态
	vector<float> err;                 //每一特征点计算误差

	int MAX_POINT_COUNT = 5;          //角点个数控制
		
	cap.open(path);
	if (!cap.isOpened())
	{
		cout << "无法打开视频文件：" << path << endl;
		//return;
	}

	for(;;)
	{
		cap >> source;
		if (source.empty())
		{
			break;
		}

		cvtColor(source, gray, COLOR_BGR2GRAY);  //灰度转换
		//以下是处理
		if (points[0].size() < 10) //点数太少，重新检测特征点
		{
			goodFeaturesToTrack(gray, points[0], MAX_POINT_COUNT,0.01,20); //找到好的特征点，可以计算Harris角点和shi-tomasi角点，但默认情况下计算的是shi-tomasi角点
		}

		if (lastGray.empty())
		{
			gray.copyTo(lastGray);
		}

		//计算光流：K-L金字塔
		//calcOpticalFlowPyrLK(标定图像的灰度图，搜寻的图像的灰度图，输入的标定图像的特征点，输出场景的特征点，输出状态向量，输出错误向量);
		//必须和其他的角点识别算法进行搭配使用，比如goodFeaturesToTrack，将其他的角点识别算法中获得的角点作为光流算法的输入的标定图像的特征点
		calcOpticalFlowPyrLK(lastGray, gray, points[0], points[1], status, err);  
		
		//下面删除掉误判点
		int counter = 0;

		for (int i = 0; i < points[1].size(); i++)
		{
			double dist = norm(points[1][i] - points[0][i]);
			if (status[i]&&dist>=2.0&&dist<=20.0)  //合理的特征追踪点，太大可能估计错误，太小可能是噪声。
			{
				points[0][counter] = points[0][i];
				points[1][counter++] = points[1][i];
			}
		}
		points[0].resize(counter);  
		points[1].resize(counter);

		//显示特征点和运动轨迹
		source.copyTo(result);
		for (int i = 0; i < points[1].size(); i++)
		{
			line(result, points[0][i], points[1][i], Scalar(0, 0, 0xff));   //上一帧和当前帧之间画一个线段
			circle(result, points[1][i], 3, Scalar(0, 0xff, 0));  //画圆
		}

		swap(points[0], points[1]);
		swap(lastGray, gray);

		imshow("源图像", source);
		imshow("检测结果",result);

		//以下检测是否终止（按下ESC终止，对应ASCII 27）
		char key = waitKey(100);
		if (key==27)
		{
			break;
		}
	}
	waitKey(0);
}